package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/charmbracelet/bubbles/list"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
)

type fileItem struct {
	name string
	path string
	dir  bool
}

func (f fileItem) Title() string       { return f.name }
func (f fileItem) Description() string { return f.path }
func (f fileItem) FilterValue() string { return f.name }

type model struct {
	list         list.Model
	currentDir   string
	modified     bool
	clipboard    string
	clipboardOp  string // "cut" or "copy"
	showDetails  bool
	detailsText  string
}

func initialModel() model {
	currentDir, _ := os.Getwd()
	items := getDirectoryItems(currentDir)

	delegate := list.NewDefaultDelegate()
	delegate.Styles.SelectedTitle = lipgloss.NewStyle().Foreground(lipgloss.Color("212"))
	delegate.Styles.SelectedDesc = delegate.Styles.SelectedTitle.Copy()

	l := list.New(items, delegate, 0, 0)
	l.Title = "File Manager"
	l.Styles.Title = lipgloss.NewStyle().MarginLeft(2)

	return model{
		list:       l,
		currentDir: currentDir,
		modified:   false,
	}
}

func getDirectoryItems(dir string) []list.Item {
	files, err := os.ReadDir(dir)
	if err != nil {
		return nil
	}

	var items []list.Item
	for _, file := range files {
		item := fileItem{
			name: file.Name(),
			path: filepath.Join(dir, file.Name()),
			dir:  file.IsDir(),
		}
		if item.dir {
			item.name += "/"
		}
		items = append(items, item)
	}
	return items
}

func (m model) Init() tea.Cmd {
	return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "q":
			if m.modified {
				return m, tea.Quit
			}
			return m, tea.Quit
		case "up", "k":
			m.list.CursorUp()
		case "down", "j":
			m.list.CursorDown()
		case "left", "h":
			parentDir := filepath.Dir(m.currentDir)
			m.currentDir = parentDir
			m.list.SetItems(getDirectoryItems(parentDir))
		case "right", "l", "enter":
			selectedItem := m.list.SelectedItem().(fileItem)
			if selectedItem.dir {
				m.currentDir = selectedItem.path
				m.list.SetItems(getDirectoryItems(selectedItem.path))
			} else {
				// Open file with default application
				fmt.Printf("Opening file: %s\n", selectedItem.path)
			}
		case " ":
			selectedItem := m.list.SelectedItem().(fileItem)
			m.showDetails = true
			m.detailsText = fmt.Sprintf("Details for: %s\nPath: %s\nIs Directory: %v", selectedItem.name, selectedItem.path, selectedItem.dir)
		case "esc":
			m.showDetails = false
		case "ctrl+s":
			m.modified = false
		case "ctrl+x":
			selectedItem := m.list.SelectedItem().(fileItem)
			m.clipboard = selectedItem.path
			m.clipboardOp = "cut"
		case "ctrl+c":
			selectedItem := m.list.SelectedItem().(fileItem)
			m.clipboard = selectedItem.path
			m.clipboardOp = "copy"
		case "ctrl+v":
			if m.clipboard != "" {
				dest := filepath.Join(m.currentDir, filepath.Base(m.clipboard))
				if m.clipboardOp == "cut" {
					os.Rename(m.clipboard, dest)
					m.clipboard = ""
				} else if m.clipboardOp == "copy" {
					copyFile(m.clipboard, dest)
				}
				m.modified = true
				m.list.SetItems(getDirectoryItems(m.currentDir))
			}
		case "delete":
			selectedItem := m.list.SelectedItem().(fileItem)
			os.RemoveAll(selectedItem.path)
			m.modified = true
			m.list.SetItems(getDirectoryItems(m.currentDir))
		}
	case tea.WindowSizeMsg:
		m.list.SetWidth(msg.Width)
		m.list.SetHeight(msg.Height - 2)
	}

	var cmd tea.Cmd
	m.list, cmd = m.list.Update(msg)
	return m, cmd
}

func (m model) View() string {
	if m.showDetails {
		return lipgloss.NewStyle().Padding(1, 2).Render(m.detailsText)
	}

	status := "unmodified"
	if m.modified {
		status = "modified"
	}
	modeline := fmt.Sprintf("<%s | %s>", m.currentDir, status)

	return lipgloss.JoinVertical(
		lipgloss.Left,
		m.list.View(),
		lipgloss.NewStyle().PaddingLeft(2).Render(modeline),
	)
}

func copyFile(src, dst string) error {
	input, err := os.ReadFile(src)
	if err != nil {
		return err
	}
	return os.WriteFile(dst, input, 0644)
}

func main() {
	p := tea.NewProgram(initialModel(), tea.WithAltScreen())
	if err := p.Start(); err != nil {
		fmt.Printf("Error running program: %v\n", err)
		os.Exit(1)
	}
}
